unit AVData;

interface
uses SysUtils, Classes, Generics.Collections, Generics.Defaults, Rtti, TypInfo,
     AVTypes, AVRes, Math, System.StrUtils;

type

  TAVData = class;
  TAVColumns = class;
  TAVRows = class;

  TAVFilterStatus = (fstUnchanged, fstInc, fstDec, fstDif);

  TAVFilterInfo = class(TObject)
  private
    fFilter, fUpperFilter: string;
    fUpperFilters: TStrings;
    fFilterStatus: TAVFilterStatus;
    procedure SetFilter(value: string);
    function GetHasFilter: boolean;
  public
    constructor Create;
    destructor Destroy; override;
    function Accept(text: string): boolean;

    property Filter: string read fFilter write SetFilter;
    property FilterStatus: TAVFilterStatus read fFilterStatus write fFilterStatus;
    property HasFilter: boolean read GetHasFilter;
  end;

  TAVBaseColumn = class(TObject)
  private
    fOwner: TAVColumns;
    fName: string;
    fType: TAVType;
    fRows: TAVRows;
    fProperties: TAVProperties;
    fFilterInfo: TAVFilterInfo;
    procedure SetName(NewName: string);
    function GetFilter: string;
  protected
    function GetCount: integer; virtual; abstract;
    procedure SetCount(value: integer); virtual; abstract;
    function GetItem(index: integer): TAVValue; virtual; abstract;
    function GetDisplayItem(index: integer): TAVValue;
    function GetHidenItem(index: integer): TAVValue;
    procedure Delete(index: integer); virtual; abstract;
    function Compare(Left, Right: integer): integer; virtual; abstract;
    procedure RemoveDeleted(infoCount: integer); virtual; abstract;
    function GetOldValue: TAVValue; virtual; abstract;
    function GetNewValue: TAVValue; virtual; abstract;

    property Count: integer read GetCount write SetCount;
    property FilterInfo: TAVFilterInfo read fFilterInfo;
  public
    constructor Create(AOwner: TAVColumns; AType: TAVType);
    destructor Destroy; override;

    function IndexInRange(index: integer): boolean; inline;
    function CloneNew(AOwner: TAVColumns): TAVBaseColumn; virtual; abstract;
    procedure CopyFrom(Source: TAVBaseColumn);
    function AplyNewDisplayValue(ToIndex: integer): boolean; virtual; abstract;

    property Name: string read fName write SetName;
    property Items[index: integer]: TAVValue read GetItem; default;
    property DisplayItems[index: integer]: TAVValue read GetDisplayItem;
    property HidenItems[index: integer]: TAVValue read GetHidenItem;
    property ValueType: TAVType read fType;
    property RowCount: integer read GetCount;
    property Properties: TAVProperties read fProperties;
    property OldValue: TAVValue read GetOldValue;
    property NewValue: TAVValue read GetNewValue;
    property Filter: string read GetFilter;
    property Rows: TAVRows read fRows;
  end;

  TAVColumn<T> = class(TAVBaseColumn)
  private
    fList: TList<T>;
    fValue: TAVValue;
    fCompareValue: TAVValue;
    fOldValue: T;
    fNewValue: T;
    function GetValue(index: integer): T;
    procedure SetValue(index: integer; value: T);
    function GetDisplayValue(index: integer): T;
    procedure SetDisplayValue(index: integer; value: T);
    function GetHidenValue(index: integer): T;
    procedure SetHidenValue(index: integer; value: T);
    function GetPValue(index: integer): Pointer;
    function GetDisplayPValue(index: integer): Pointer;
    function GetHidenPValue(index: integer): Pointer;
  protected
    function GetCount: integer; override;
    procedure SetCount(value: integer); override;
    function GetItem(index: integer): TAVValue; override;
    function Compare(Left, Right: integer): integer; override;
    procedure Delete(index: integer); override;
    procedure RemoveDeleted(infoCount: integer); override;
    function GetOldValue: TAVValue; override;
    function GetNewValue: TAVValue; override;
  public
    constructor Create(AOwner: TAVColumns);
    destructor Destroy; override;
    procedure Clear;
    function CloneNew(AOwner: TAVColumns): TAVBaseColumn; override;
    function AplyNewDisplayValue(ToIndex: integer): boolean; override;

    property Value[index: integer]: T read GetValue write SetValue; default;
    property DisplayValue[index: integer]: T read GetDisplayValue write SetDisplayValue;
    property HidenValue[index: integer]: T read GetHidenValue write SetHidenValue;
    property PValue[index: integer]: Pointer read GetPValue; //direct pointer to value in array
    property DisplayPValue[index: integer]: Pointer read GetDisplayPValue;
    property HidenPValue[index: integer]: Pointer read GetHidenPValue;
  end;

  TOnColumnAction = procedure(ACol: TAVBaseColumn) of object;

  TAVColumns = class(TObject)
  private
    fOwner: TAVData;
    fColumns: TList<TAVBaseColumn>;
    fColumnsDic: TDictionary<string,TAVBaseColumn>;
    fOnColumnAdd: TOnColumnAction;
    fOnColumnDel: TOnColumnAction;
    function GetCount: integer;
    function GetItem(index: integer): TAVBaseColumn;
    function GetByName(ColName: string): TAVBaseColumn;

    procedure CheckNewName(NewName: string);
  protected
    procedure Rename(OldName,NewName: string; Column: TAVBaseColumn);
    procedure ColumnAdded(ACol: TAVBaseColumn);
    procedure ColumnDeleted(ACol: TAVBaseColumn);
    function Remove(index: integer): TAVBaseColumn;

    property OnColumnAdd: TOnColumnAction read fOnColumnAdd write fOnColumnAdd;
    property OnColumnDel: TOnColumnAction read fOnColumnDel write fOnColumnDel;
  public
    constructor Create(AOwner: TAVData);
    destructor Destroy; override;
    procedure Clear;
    function Add<T>(ColName: string): TAVColumn<T>; overload;
    function Add(ColName: string; ColType: TAVType): TAVBaseColumn; overload;
    function Add(ColName: string; ColType: TAVType; DataFrom: TAVBaseColumn): TAVBaseColumn; overload;
    procedure Delete(index: integer); overload;
    procedure Delete(ColName: string); overload;
    function IndexOf(Col: TAVBaseColumn): integer;
    function NewColumnType(RemovedCol: TAVBaseColumn; NewType: TAVType): TAVBaseColumn;

    property Owner: TAVData read fOwner;
    property Count: integer read GetCount;
    property Items[index: integer]: TAVBaseColumn read GetItem; default;
    property ByName[ColName: string]: TAVBaseColumn read GetByName;
  end;

  TRowCountChanged = procedure(OldCount, NewCount: integer) of object;

  TAVRowStatus = (
    stNew     = 0,
    stChanged = 1,
    stDeleted = 2,
    stHiden = 3,
    stForDelete = 4
  );

  TAVRowsComparer = class(TInterfacedObject, IComparer<integer>)
  public
    SortColumns: TArray<TAVBaseColumn>;
    function Compare(const Left, Right: integer): Integer;
    function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;
  end;

  TAVRows = class(TObject)
  private
    fColumns: TAVColumns;
    fStatus: TList<integer>;
    fRowsIndex: TList<integer>;
    fHidenIndex: TList<integer>;
    fOnRowCountChanged: TEvent<TRowCountChanged>;
    fComparer: TAVRowsComparer;
    fRowCount: integer;
    function GetRowCount: integer;
    function GetHidenCount: integer;
    function GetDisplayRowCount: integer;
    procedure SetDisplayRowCount(value: integer);
    procedure ColumnAdded(ACol: TAVBaseColumn);
    procedure RowCountChanged(OldCount, NewCount: integer);
    function GetRowStatus(index: integer; status: TAVRowStatus): boolean;
    procedure SetRowStatus(index: integer; status: TAVRowStatus; value: boolean);
    function GetDisplayStatus(index: integer; status: TAVRowStatus): boolean;
    procedure SetDisplayStatus(index: integer; status: TAVRowStatus; value: boolean);
    function GetHidenRowStatus(index: integer; status: TAVRowStatus): boolean;
    procedure SetHidenRowStatus(index: integer; status: TAVRowStatus; value: boolean);
    function GetFilteredColumns: TList<TAVBaseColumn>;
    procedure FilterInc(Column: TAVBaseColumn);
    procedure FilterDec;
    procedure FilterDif;
    procedure RemoveDeletedStatus(infoCount: integer);
    procedure DeleteRealIndexesFromIndexList(indexList, deletedSortedRealIndex: TList<integer>);
  protected
    procedure DeleteDisplayRows(indexList: TList<integer>);
    procedure DeleteDisplayRow(index: integer);
    procedure HideDisplayRow(index: integer);
    procedure HideDisplayRows(indexList: TList<integer>);
    procedure UnHideRow(index: integer);
    procedure UnHideRows(indexList: TList<integer>);
    procedure UnHideAll;

    property Status: TList<integer> read fStatus;
  public
    constructor Create(AVColumns: TAVColumns);
    destructor Destroy; override;
    procedure Sort(SortColumns: TArray<TAVBaseColumn>);
    procedure Filter(ChangedColumn: TAVBaseColumn = nil);

    property RowCount: integer read GetRowCount;
    property DisplayRowCount: integer read GetDisplayRowCount write SetDisplayRowCount;
    property HidenCount: integer read GetHidenCount;
    property RowStatus[index: integer; status: TAVRowStatus]: boolean read GetRowStatus write SetRowStatus;
    property DisplayRowStatus[index: integer; status: TAVRowStatus]: boolean read GetDisplayStatus write SetDisplayStatus; default;
    property HidenRowstatus[index: integer; status: TAVRowStatus]: boolean read GetHidenRowStatus write SetHidenRowStatus;
    property RowsIndex: TList<integer> read fRowsIndex;
    property HidenIndex: TList<integer> read fHidenIndex;

    property OnRowCountChanged: TEvent<TRowCountChanged> read fOnRowCountChanged;
  end;

  TAVData = class(TObject)
  private
    fColumns: TAVColumns;
    fRows: TAVRows;
    fNullValue: TAVValue;
    fProperties: TAVProperties;
    function GetColumnCount: integer;
    function GetRowCount: integer;
    function GetDisplayRowCount: integer;
    function GetHidenCount: integer;
    procedure SetDisplayRowCount(value: integer);
    function GetValue(col, row: integer): TAVValue;
    function GetDisplayValue(col, row: integer): TAVValue;
    function GetHidenValue(col, row: integer): TAVValue;
    function GetNValue(ColName: string; row: integer): TAVValue;
    function GetDisplayNValue(ColName: string; row: integer): TAVValue;
    function GetHidenNValue(ColName: string; row: integer): TAVValue;
  public
    constructor Create;
    destructor Destroy; override;

    function AddCol<T>(ColName: string): TAVColumn<T>; overload;
    function AddCol(ColName: string; ColType: TAVType): TAVBaseColumn; overload;
    procedure DeleteDisplayRows(indexList: TList<integer>);
    procedure DeleteDisplayRow(rowIndex: integer);
    procedure Sort(SortColumns: TArray<TAVBaseColumn>); overload;
    procedure Sort(SortColumns: TArray<string>); overload;
    procedure Sort(SortColumn: TAVBaseColumn); overload;
    procedure Sort(SortColumn: string); overload;
    function GetBookmark(row: integer): integer;
    function FindRowByBookmark(bookmark: integer): integer;
    procedure SetFilter(Column: TAVBaseColumn; filter: string); overload;
    procedure SetFilter(Column: string; filter: string); overload;

    property Columns: TAVColumns read fColumns;
    property Rows: TAVRows read fRows;
    property ColumnCount: integer read GetColumnCount;
    property DisplayRowCount: integer read GetDisplayRowCount write SetDisplayRowCount;
    property RowCount: integer read GetRowCount;
    property HidenCount: integer read GetHidenCount;
    property Value[col, row: integer]: TAVValue read GetValue;
    property NValue[ColName: string; row: integer]: TAVValue read GetNValue; default;
    property DisplayValue[col, row: integer]: TAVValue read GetDisplayValue;
    property DisplayNValue[ColName: string; row: integer]: TAVValue read GetDisplayNValue;
    property HidenValue[col, row: integer]: TAVValue read GetHidenValue;
    property HidenNValue[ColName: string; row: integer]: TAVValue read GetHidenNValue;
    property Properties: TAVProperties read fProperties;
  end;

implementation
uses AVUtils;

//TAVFilterInfo

    constructor TAVFilterInfo.Create;
    begin
      inherited;
      fUpperFilters := TStringList.Create;
    end;

    destructor TAVFilterInfo.Destroy;
    begin
      fUpperFilters.Free;
      inherited;
    end;

    procedure TAVFilterInfo.SetFilter(value: string);
    begin
      if (fUpperFilter = '') or (Pos(fUpperFilter, UpperCase(value)) > 0) then fFilterStatus := fstInc else
      if (value = '') or (Pos(UpperCase(value), fUpperFilter) > 0) then fFilterStatus := fstDec else fFilterStatus := fstDif;
      fFilter := value;
      fUpperFilter := UpperCase(fFilter);
      Split(fUpperFilter, fUpperFilters);
    end;

    function TAVFilterInfo.Accept(text: string): boolean;
    var i: integer;
    begin
      if fUpperFilters.Count = 0 then Exit(true);
      text := UpperCase(text);
      for i := 0 to fUpperFilters.Count - 1 do
      begin
        if Pos(fUpperFilters[i], text) = 0 then Exit(false);
      end;
      Result := true;
    end;

    function TAVFilterInfo.GetHasFilter: boolean;
    begin
      Result := fUpperFilters.Count > 0;
    end;

//TAVBaseColumn

    constructor TAVBaseColumn.Create(AOwner: TAVColumns; AType: TAVType);
    begin
      inherited Create;
      fProperties := TAVProperties.Create;
      fOwner := AOwner;
      fType := AType;
      fRows := AOwner.fOwner.fRows;
      fFilterInfo := TAVFilterInfo.Create;
    end;

    destructor TAVBaseColumn.Destroy;
    begin
      fProperties.Free;
      fFilterInfo.Free;
      inherited Destroy;
    end;

    procedure TAVBaseColumn.SetName(NewName: string);
    begin
      if (fOwner <> nil) and (LowerCase(fName) <> LowerCase(NewName))
        then fOwner.Rename(fName, NewName, Self)
        else fName := NewName;
    end;

    function TAVBaseColumn.GetFilter: string;
    begin
      Result := fFilterInfo.Filter;
    end;

    function TAVBaseColumn.IndexInRange(index: integer): boolean;
    begin
      Result := (index >= 0) and (index < Count);
    end;

    procedure TAVBaseColumn.CopyFrom(Source: TAVBaseColumn);
    var i, n: integer;
    begin
      n := Math.Min(Count, Source.Count);
      for i := 0 to n - 1 do begin
        Self.Items[i].CopyFrom(Source.Items[i]);
      end;
    end;

    function TAVBaseColumn.GetDisplayItem(index: integer): TAVValue;
    begin
      Result := GetItem(Rows.RowsIndex[index]);
    end;

    function TAVBaseColumn.GetHidenItem(index: integer): TAVValue;
    begin
      Result := GetItem(Rows.HidenIndex[index]);
    end;

//TAVColumn<T>

    constructor TAVColumn<T>.Create(AOwner: TAVColumns);
    begin
      fValue := TAVValue.Create;
      fCompareValue := TAVValue.Create;
      fValue.PointTo<T>(nil); //will throw exception if <T> is not supported type
      fCompareValue.PointTo<T>(nil);
      inherited Create(AOwner, fValue.ValueType);
      fList := TList<T>.Create;
    end;

    destructor TAVColumn<T>.Destroy;
    begin
      Clear;
      fList.Free;
      fCompareValue.Free;
      fValue.Free;
      inherited Destroy;
    end;

    function TAVColumn<T>.CloneNew(AOwner: TAVColumns): TAVBaseColumn;
    begin
      Result := TAVColumn<T>.Create(AOwner);
    end;

    function TAVColumn<T>.AplyNewDisplayValue(ToIndex: integer): boolean;
    begin
      if (ToIndex < 0) or (ToIndex >= Rows.RowsIndex.Count) then Exit(false);
      fOldValue := fList[Rows.RowsIndex[ToIndex]];
      fCompareValue.PointToNewValue(@(fList.List[Rows.RowsIndex[ToIndex]]));
      if NewValue.CompareTo(fCompareValue) <> 0 then
      begin
        fList[Rows.RowsIndex[ToIndex]] := fNewValue;
        Result := true;
      end else
        Result := false;
    end;

    procedure TAVColumn<T>.Clear;
    begin
      fList.Clear;
    end;

    function TAVColumn<T>.GetCount: integer;
    begin
      Result := fList.Count;
    end;

    procedure TAVColumn<T>.SetCount(value: integer);
    begin
      if value < 0 then value := 0;
      while fList.Count < value do fList.Add(default(T));
      if fList.Count > value then fList.Count := value;
    end;

    function TAVColumn<T>.GetItem(index: integer): TAVValue;
    begin
      fValue.PointToNewValue(@(fList.List[index]));
      Result := fValue;
    end;

    function TAVColumn<T>.Compare(Left, Right: integer): integer;
    begin
      fValue.PointToNewValue(@(fList.List[Left]));
      fCompareValue.PointToNewValue(@(fList.List[Right]));
      Result := fValue.CompareTo(fCompareValue);
    end;

    procedure TAVColumn<T>.Delete(index: integer);
    begin
      fList.Delete(index);
    end;

    procedure TAVColumn<T>.RemoveDeleted(infoCount: integer);
    var newList, tempList: TList<T>;
        i: integer;
    begin
      if infoCount < 100 then begin
        for i := fList.Count - 1 downto 0 do begin
          if Rows.RowStatus[i, stForDelete] then fList.Delete(i);
        end;
      end else begin
        newList := TList<T>.Create;
        for i := 0 to fList.Count - 1 do begin
          if not Rows.RowStatus[i, stForDelete] then begin
            newList.Add(fList[i]);
          end;
        end;
        tempList := fList;
        fList := newList;
        tempList.Free;
      end;
    end;

    function TAVColumn<T>.GetOldValue: TAVValue;
    begin
      fValue.PointToNewValue(@fOldValue);
      Result := fValue;
    end;

    function TAVColumn<T>.GetNewValue: TAVValue;
    begin
      fValue.PointToNewValue(@fNewValue);
      Result := fValue;
    end;

    function TAVColumn<T>.GetDisplayValue(index: integer): T;
    begin
      Result := fList[Rows.RowsIndex[index]];
    end;

    procedure TAVColumn<T>.SetDisplayValue(index: integer; value: T);
    begin
      fList[Rows.RowsIndex[index]] := value;
    end;

    function TAVColumn<T>.GetValue(index: integer): T;
    begin
      Result := fList[index];
    end;

    procedure TAVColumn<T>.SetValue(index: integer; value: T);
    begin
      fList[index] := value;
    end;

    function TAVColumn<T>.GetHidenValue(index: integer): T;
    begin
      Result := fList[Rows.HidenIndex[index]];
    end;

    procedure TAVColumn<T>.SetHidenValue(index: integer; value: T);
    begin
      fList[Rows.HidenIndex[index]] := value;
    end;

    function TAVColumn<T>.GetDisplayPValue(index: integer): Pointer;
    begin
      Result := @(fList.List[Rows.RowsIndex[index]]);
    end;

    function TAVColumn<T>.GetPValue(index: integer): Pointer;
    begin
      Result := @(fList.List[index]);
    end;

    function TAVColumn<T>.GetHidenPValue(index: integer): Pointer;
    begin
      Result := @(fList.List[Rows.HidenIndex[index]]);
    end;

//TAVColumns

    constructor TAVColumns.Create(AOwner: TAVData);
    begin
      inherited Create;
      fOwner := AOwner;
      fColumns := TList<TAVBaseColumn>.Create;
      fColumnsDic := TDictionary<string,TAVBaseColumn>.Create;
    end;

    destructor TAVColumns.Destroy;
    begin
      Clear;
      fColumnsDic.Free;
      fColumns.Free;
      inherited Destroy;
    end;

    procedure TAVColumns.Clear;
    var
      i: integer;
      col: TAVBaseColumn;
    begin
      fColumnsDic.Clear;
      for i := fColumns.Count - 1 downto 0 do begin
        col := fColumns[i];
        fColumns.Delete(i);
        ColumnDeleted(col);
        col.Free;
      end;
    end;

    function TAVColumns.GetCount: integer;
    begin
      Result := fColumns.Count;
    end;

    function TAVColumns.GetItem(index: integer): TAVBaseColumn;
    begin
      Result := fColumns[index];
    end;

    function TAVColumns.GetByName(ColName: string): TAVBaseColumn;
    begin
      if not fColumnsDic.TryGetValue(LowerCase(ColName), Result)
        then Result := nil;
    end;

    procedure TAVColumns.CheckNewName(NewName: string);
    var
      col: TAVBaseColumn;
    begin
      if NewName = '' then raise Exception.CreateRes(@SColumnNameCannotBeEmpty);
      if fColumnsDic.TryGetValue(LowerCase(NewName), col)
        then raise Exception.CreateResFmt(@SColumnAlreadyExist, [NewName]);
    end;

    function TAVColumns.Add<T>(ColName: string): TAVColumn<T>;
    begin
      CheckNewName(ColName);
      Result := TAVColumn<T>.Create(Self);
      Result.fName := ColName;
      fColumns.Add(Result);
      fColumnsDic.Add(LowerCase(ColName), Result);
      ColumnAdded(Result);
    end;

    function TAVColumns.Add(ColName: string; ColType: TAVType): TAVBaseColumn;
    begin
      case ColType of
        avtString: Result := Add<string>(ColName);
        avtByte: Result := Add<byte>(ColName);
        avtInt16: Result := Add<Int16>(ColName);
        avtInt32: Result := Add<Int32>(ColName);
        avtInt64: Result := Add<Int64>(ColName);
        avtNativeInt: Result := Add<NativeInt>(ColName);
        avtDouble: Result := Add<double>(ColName);
        avtObject: Result := Add<TObject>(ColName);
        avtDate: Result := Add<TDateTime>(ColName);
        avtBool: Result := Add<boolean>(ColName);
        avtValue: Result := Add<TValue>(ColName);
        avtVariant: Result := Add<Variant>(ColName);
        avtBitArray: Result := Add<TBitArray>(ColName);
        avtRecord: raise Exception.Create('For record types use generic Add<T> method');
        else raise Exception.Create('Not supported type');
      end;
    end;

    function TAVColumns.Add(ColName: string; ColType: TAVType; DataFrom: TAVBaseColumn): TAVBaseColumn;
    begin
      Result := Add(ColName, ColType);
      if DataFrom <> nil then begin
        Result.CopyFrom(DataFrom);
      end;
    end;

    function TAVColumns.Remove(index: integer): TAVBaseColumn;
    begin
      if (index >= 0) and (index < Count) then begin
        fColumnsDic.Remove(LowerCase(fColumns[index].Name));
        Result := fColumns[index];
        fColumns.Delete(index);
      end else Result := nil;
    end;

    procedure TAVColumns.Delete(index: integer);
    var col: TAVBaseColumn;
    begin
      col := Remove(index);
      if col <> nil then begin
        ColumnDeleted(col);
        col.Free;
      end;
    end;

    procedure TAVColumns.Delete(ColName: string);
    var col: TAVBaseColumn;
        n: integer;
    begin
      if fColumnsDic.TryGetValue(LowerCase(ColName), col) then
      begin
        n := fColumns.IndexOf(col);
        Delete(n);
      end;
    end;

    function TAVColumns.IndexOf(Col: TAVBaseColumn): integer;
    begin
      Result := fColumns.IndexOf(Col);
    end;

    function TAVColumns.NewColumnType(RemovedCol: TAVBaseColumn; NewType: TAVType): TAVBaseColumn;
    var col: TAVBaseColumn;
        n: integer;
    begin
      n := fColumns.IndexOf(RemovedCol);
      if n >= 0 then begin
        col := Remove(n);
        Result := Add(col.Name, NewType, col);
        ColumnDeleted(col);
        col.Free;
      end else raise Exception.Create('Column for remove not found');
    end;

    procedure TAVColumns.Rename(OldName,NewName: string; Column: TAVBaseColumn);
    var
      col: TAVBaseColumn;
    begin
      CheckNewName(NewName);
      col := GetByName(OldName);
      if col = nil then raise Exception.CreateResFmt(@SColumnNotExist, [OldName]);
      if col <> Column then raise Exception.Create('Internal error: invalid column in argument');
      fColumnsDic.Remove(LowerCase(OldName));
      Column.fName := NewName;
      fColumnsDic.Add(LowerCase(NewName), Column);
    end;

    procedure TAVColumns.ColumnAdded(ACol: TAVBaseColumn);
    begin
      if Assigned(fOnColumnAdd) then fOnColumnAdd(ACol);
    end;

    procedure TAVColumns.ColumnDeleted(ACol: TAVBaseColumn);
    begin
      if Assigned(fOnColumnDel) then fOnColumnDel(ACol);
    end;

//TAVRowsComparer

    function TAVRowsComparer.Compare(const Left, Right: integer): Integer;
    var i: integer;
    begin
      Result := 0;
      for i := Low(SortColumns) to High(SortColumns) do begin
        Result := SortColumns[i].Compare(Left, Right);
//        Result := SortColumns[i].RealItems[Left].CompareTo(SortColumns[i].RealItems[Right]);
        if Result <> 0 then break;
      end;
    end;

    function TAVRowsComparer.QueryInterface(const IID: TGUID; out Obj): HResult;
    begin
      if GetInterface(IID, Obj) then Result := 0 else Result := E_NOINTERFACE;
    end;

    function TAVRowsComparer._AddRef: Integer;
    begin
    end;

    function TAVRowsComparer._Release: Integer;
    begin
    end;

//TAVRows

    constructor TAVRows.Create(AVColumns: TAVColumns);
    begin
      inherited Create;
      fRowsIndex := TList<integer>.Create;
      fHidenIndex := TList<integer>.Create;
      fStatus := TList<integer>.Create;
      fColumns := AVColumns;
      fColumns.OnColumnAdd := ColumnAdded;
      fOnRowCountChanged := TEvent<TRowCountChanged>.Create(
           procedure(proc: TRowCountChanged; args: TArray<TValue>)
           begin
             proc(args[0].AsInteger, args[1].AsInteger);
           end
        );
      fComparer := TAVRowsComparer.Create;
    end;

    destructor TAVRows.Destroy;
    begin
      fComparer.Free;
      fStatus.Free;
      fRowsIndex.Free;
      fHidenIndex.Free;
      inherited Destroy;
    end;

    procedure TAVRows.Sort(SortColumns: TArray<TAVBaseColumn>);
    begin
      fComparer.SortColumns := SortColumns;
      fRowsIndex.Sort(fComparer);
    end;

    procedure TAVRows.FilterInc(Column: TAVBaseColumn);
    var indexList: TList<integer>;
        i: integer;
    begin
      indexList := TList<integer>.Create;
      try
        for i := 0 to fRowsIndex.Count - 1 do
        begin
          if not Column.FilterInfo.Accept(Column.DisplayItems[i].AsString)
            then indexList.Add(i);
        end;
        HideDisplayRows(indexList);
      finally
        indexList.Free;
      end;
    end;

    function TAVRows.GetFilteredColumns: TList<TAVBaseColumn>;
    var i: integer;
    begin
      Result := TList<TAVBaseColumn>.Create;
      for i := 0 to fColumns.Count - 1 do
        if fColumns[i].FilterInfo.HasFilter then Result.Add(fColumns[i]);
    end;

    procedure TAVRows.FilterDec;
    var columnList: TList<TAVBaseColumn>;
        i,j: integer;
        indexList: TList<integer>;
        Accept: boolean;
    begin
      columnList := GetFilteredColumns;
      indexList := TList<integer>.Create;
      try
        if columnList.Count = 0 then begin UnHideAll; exit; end;

        for i := 0 to fHidenIndex.Count - 1 do
        begin
          Accept := true;
          for j := 0 to columnList.Count - 1 do begin
            Accept := columnList[j].FilterInfo.Accept(columnList[j].HidenItems[i].AsString);
            if not Accept then break;
          end;
          if Accept then indexList.Add(i);
        end;

        UnHideRows(indexList);
      finally
        columnList.Free;
        indexList.Free;
      end;
    end;

    procedure TAVRows.FilterDif;
    var columnList: TList<TAVBaseColumn>;
        i,j: integer;
        forHideList, forShowList: TList<integer>;
        Accept: boolean;
    begin
      columnList := GetFilteredColumns;
      forHideList := TList<integer>.Create;
      forShowList := TList<integer>.Create;
      try
        if columnList.Count = 0 then begin UnHideAll; exit; end;

        for i := 0 to fHidenIndex.Count - 1 do
        begin
          Accept := true;
          for j := 0 to columnList.Count - 1 do begin
            Accept := columnList[j].FilterInfo.Accept(columnList[j].HidenItems[i].AsString);
            if not Accept then break;
          end;
          if Accept then forShowList.Add(i);
        end;

        for i := 0 to fRowsIndex.Count - 1 do
        begin
          Accept := true;
          for j := 0 to columnList.Count - 1 do begin
            Accept := columnList[j].FilterInfo.Accept(columnList[j].Items[i].AsString);
            if not Accept then break;
          end;
          if not Accept then forHideList.Add(i);
        end;

        UnHideRows(forShowList);
        HideDisplayRows(forHideList);
      finally
        columnList.Free;
        forHideList.Free;
        forShowList.Free;
      end;
    end;

    procedure TAVRows.Filter(ChangedColumn: TAVBaseColumn = nil);
    var filterStatus: TAVFilterStatus;
    begin
      if ChangedColumn = nil
        then filterStatus := fstDif
        else filterStatus := ChangedColumn.FilterInfo.FilterStatus;

      case filterStatus of
        fstUnchanged: exit;
        fstInc: FilterInc(ChangedColumn);
        fstDec: FilterDec;
        fstDif: FilterDif;
      end;
    end;

    procedure TAVRows.RowCountChanged(OldCount, NewCount: integer);
    begin
      if fOnRowCountChanged.HasMember then
        fOnRowCountChanged.Execute(TArray<TValue>.Create(OldCount, NewCount));
    end;

    function TAVRows.GetDisplayRowCount: integer;
    begin
      Result := fRowsIndex.Count;
    end;

    procedure TAVRows.SetDisplayRowCount(value: integer);
    var i, currCount: integer;
        indexList: TList<integer>;
        dif: integer;
    begin
      if value < 0 then value := 0;
      currCount := fRowsIndex.Count;
      if currCount <> value then begin
        if value > currCount then begin
          dif := value - currCount;
          for i  := 0 to fColumns.Count - 1 do
            fColumns[i].Count := fRowCount + dif;
          fStatus.Count := fRowCount + dif;
          for i := currCount to value - 1 do
            fRowsIndex.Add(i);
          fRowCount := fRowCount + dif;
          RowCountChanged(currCount, value);
        end else begin
          indexList := TList<integer>.Create;
          try
            for i := value to currCount - 1 do indexList.Add(i);
            DeleteDisplayRows(indexList);
          finally
            indexList.Free;
          end;
        end;
      end;
    end;

    function TAVRows.GetRowCount: integer;
    begin
      Result := fRowCount;
    end;

    function TAVRows.GetHidenCount: integer;
    begin
      Result := fHidenIndex.Count;
    end;

    procedure TAVRows.RemoveDeletedStatus(infoCount: integer);
    var i: integer;
        newList, tmpList: TList<integer>;
    begin
      if infoCount < 100 then
      begin
        for i := fStatus.Count - 1 downto 0 do
          if GetBit(fStatus.List[i], Integer(stForDelete)) then fStatus.Delete(i);
      end else
      begin
        newList := TList<integer>.Create;
        newList.Capacity := fStatus.Count - infoCount;
        for i := 0 to fStatus.Count - 1 do
          if not GetBit(fStatus.List[i], Integer(stForDelete)) then newList.Add(fStatus[i]);
        tmpList := fStatus;
        fStatus := newList;
        tmpList.Free;
      end;
    end;

    procedure TAVRows.DeleteRealIndexesFromIndexList(indexList, deletedSortedRealIndex: TList<integer>);
    var newIndex: TList<integer>;
        i, min, max, v, findIndex: integer;
        deleted: boolean;
    begin
      min := deletedSortedRealIndex[0];
      max := deletedSortedRealIndex[deletedSortedRealIndex.Count - 1];

      newIndex := TList<integer>.Create;
      try
        for i := 0 to indexList.Count - 1 do
        begin
          v := indexList[i];
          deleted := false;
          if v >= min then
          begin
            if v > max then v := v - deletedSortedRealIndex.Count
            else begin
              if deletedSortedRealIndex.BinarySearch(v, findIndex)
              then deleted := true
              else v := v - findIndex;
            end;
          end;
          if not deleted then newIndex.Add(v);
        end;
        newIndex.Capacity := newIndex.Count;
        indexList.Clear;
        indexList.AddRange(newIndex.List);
      finally
        newIndex.Free;
      end;
    end;

    procedure TAVRows.DeleteDisplayRows(indexList: TList<integer>);
    var realIndex: TList<integer>;
        i: integer;
        currCount: integer;
    begin
      if indexList.Count = 0 then exit;
      currCount := fRowsIndex.Count;

      realIndex := TList<integer>.Create;
      try
        //mark rows for delete
        for i := 0 to indexList.Count - 1 do
          SetBit(fStatus.List[fRowsIndex[indexList[i]]], Integer(stForDelete), true);

        //create sorted real index
        realIndex.Capacity := fStatus.Count - (indexList.Count div 2);
        for i := 0 to fStatus.Count - 1 do
          if GetBit(fStatus.List[i], Integer(stForDelete)) then realIndex.Add(i);

        //delete marked rows
        for i  := 0 to fColumns.Count - 1 do
          fColumns[i].RemoveDeleted(realIndex.Count);
        RemoveDeletedStatus(realIndex.Count);

        //delete indexes for deleted rows and decrement indexes for rows which is after deleted rows
        DeleteRealIndexesFromIndexList(RowsIndex, realIndex);
        DeleteRealIndexesFromIndexList(HidenIndex, realIndex);
      finally
        realIndex.Free;
      end;
      fRowCount := fRowCount - indexList.Count;
      RowCountChanged(currCount, fRowsIndex.Count);
    end;

    procedure TAVRows.DeleteDisplayRow(index: integer);
    var i, V, currV: integer;
    begin
      if (index >= 0) and (index < fRowsIndex.Count) then begin
        for i := 0 to fColumns.Count - 1 do
          fColumns[i].Delete(fRowsIndex[index]);
        fStatus.Delete(fRowsIndex[index]);
        V := fRowsIndex[index];
        fRowsIndex.Delete(index);
        for i := 0 to fRowsIndex.Count - 1 do begin
          currV := fRowsIndex[i];
          if currV > v then fRowsIndex[i] := currV - 1;
        end;
        fRowCount := fRowCount - 1;
        RowCountChanged(fRowsIndex.Count + 1, fRowsIndex.Count);
      end;
    end;

    procedure TAVRows.HideDisplayRow(index: integer);
    begin
      DisplayRowStatus[index, stHiden] := true;
      fHidenIndex.Add(fRowsIndex[index]);
      fRowsIndex.Delete(index);
    end;

    procedure TAVRows.HideDisplayRows(indexList: TList<integer>);
    var i: integer;
        newIndex, tmpIndex: TList<integer>;
    begin
      if indexList.Count = 0 then Exit;
      if indexList.Count = 1 then begin
        HideDisplayRow(indexList[0]);
        Exit;
      end;

      for i := 0 to indexList.Count - 1 do begin
        DisplayRowStatus[indexList[i], stHiden] := true;
        fHidenIndex.Add(fRowsIndex[indexList[i]]);
      end;

      newIndex := TList<integer>.Create;
      for i := 0 to fRowsIndex.Count - 1 do begin
        if not DisplayRowStatus[i, stHiden] then newIndex.Add(fRowsIndex[i]);
      end;

      tmpIndex := fRowsIndex;
      fRowsIndex := newIndex;
      tmpIndex.Free;
    end;

    procedure TAVRows.UnHideRow(index: integer);
    begin
      HidenRowstatus[index, stHiden] := false;
      fRowsIndex.Add(fHidenIndex[index]);
      fHidenIndex.Delete(index);
    end;

    procedure TAVRows.UnHideRows(indexList: TList<integer>);
    var i: integer;
        newIndex, tmpIndex: TList<integer>;
    begin
      if indexList.Count = 0 then Exit;
      if indexList.Count = 1 then begin
        UnHideRow(indexList[0]);
        exit;
      end;

      for i := 0 to indexList.Count - 1 do begin
        HidenRowStatus[indexList[i], stHiden] := false;
        fRowsIndex.Add(fHidenIndex[indexList[i]]);
      end;

      newIndex := TList<integer>.Create;
      for i := 0 to fHidenIndex.Count - 1 do begin
        if HidenRowStatus[i, stHiden] then newIndex.Add(fHidenIndex[i]);
      end;

      tmpIndex := fHidenIndex;
      fHidenIndex := newIndex;
      tmpIndex.Free;
    end;

    procedure TAVRows.UnHideAll;
    var i: integer;
    begin
      for i := 0 to fHidenIndex.Count - 1 do
      begin
        HidenRowStatus[i, stHiden] := false;
        fRowsIndex.Add(fHidenIndex[i]);
      end;
      fHidenIndex.Clear;
    end;

    procedure TAVRows.ColumnAdded(ACol: TAVBaseColumn);
    begin
      ACol.Count := fRowsIndex.Count;
    end;

    function TAVRows.GetDisplayStatus(index: integer; status: TAVRowStatus): boolean;
    begin
      Result := GetBit(fStatus.List[fRowsIndex[index]], Integer(status));
    end;

    procedure TAVRows.SetDisplayStatus(index: integer; status: TAVRowStatus; value: boolean);
    begin
      SetBit(fStatus.List[fRowsIndex[index]], Integer(status), value);
    end;

    function TAVRows.GetRowStatus(index: integer; status: TAVRowStatus): boolean;
    begin
      Result := GetBit(fStatus.List[index], Integer(status));
    end;

    procedure TAVRows.SetRowStatus(index: integer; status: TAVRowStatus; value: boolean);
    begin
      SetBit(fStatus.List[index], Integer(status), value);
    end;

    function TAVRows.GetHidenRowStatus(index: integer; status: TAVRowStatus): boolean;
    begin
      Result := GetBit(fStatus.List[fHidenIndex[index]], Integer(status));
    end;

    procedure TAVRows.SetHidenRowStatus(index: integer; status: TAVRowStatus; value: boolean);
    begin
      SetBit(fStatus.List[fHidenIndex[index]], Integer(status), value);
    end;

//TAVData

    constructor TAVData.Create;
    begin
      inherited Create;
      fNullValue := TAVValue.Create;
      fColumns := TAVColumns.Create(Self);
      fRows := TAVRows.Create(fColumns);
      fNullValue.PointTo<string>(nil);
      fProperties := TAVProperties.Create;
    end;

    destructor TAVData.Destroy;
    begin
      fRows.Free;
      fColumns.Free;
      fNullValue.Free;
      fProperties.Free;
      inherited Destroy;
    end;

    function TAVData.GetColumnCount: integer;
    begin
      Result := fColumns.Count;
    end;

    function TAVData.GetRowCount: integer;
    begin
      Result := fRows.RowCount;
    end;

    function TAVData.GetDisplayRowCount: integer;
    begin
      Result := fRows.DisplayRowCount;
    end;

    function TAVData.GetHidenCount: integer;
    begin
      Result := fRows.HidenCount;
    end;

    procedure TAVData.SetDisplayRowCount(value: integer);
    begin
      fRows.DisplayRowCount := value;
    end;

    function TAVData.GetDisplayValue(col,row: integer): TAVValue;
    begin
      if ((col < 0) or (col >= fColumns.Count)) or
         ((row < 0) or (row >= fRows.DisplayRowCount))
      then Exit(fNullValue);

      Result := fColumns[col][row];
    end;

    function TAVData.GetValue(col, row: integer): TAVValue;
    begin
      if ((col < 0) or (col >= fColumns.Count)) and
         ((row < 0) or (row >= fRows.RowCount))
      then Exit(fNullValue);

      Result := fColumns[col].Items[row];
    end;

    function TAVData.GetHidenValue(col, row: integer): TAVValue;
    begin
      if ((col < 0) or (col >= fColumns.Count)) and
         ((row < 0) or (row >= fRows.HidenCount))
      then Exit(fNullValue);

      Result := fColumns[col].HidenItems[row];
    end;

    function TAVData.GetDisplayNValue(ColName: string; row: integer): TAVValue;
    var
      col: TAVBaseColumn;
    begin
      if (row < 0) or (row >= fRows.DisplayRowCount) then Exit(fNullValue);
      col := fColumns.ByName[ColName];
      if col = nil then Exit(fNullValue);
      Result := col.DisplayItems[row];
    end;

    function TAVData.GetNValue(ColName: string; row: integer): TAVValue;
    var
      col: TAVBaseColumn;
    begin
      if (row < 0) or (row >= fRows.RowCount) then Exit(fNullValue);
      col := fColumns.ByName[ColName];
      if col = nil then Exit(fNullValue);
      Result := col.Items[row];
    end;

    function TAVData.GetHidenNValue(ColName: string; row: integer): TAVValue;
    var
      col: TAVBaseColumn;
    begin
      if (row < 0) or (row >= fRows.HidenCount) then Exit(fNullValue);
      col := fColumns.ByName[ColName];
      if col = nil then Exit(fNullValue);
      Result := col.HidenItems[row];
    end;

    function TAVData.AddCol<T>(ColName: string): TAVColumn<T>;
    begin
      Result := fColumns.Add<T>(ColName);
    end;

    function TAVData.AddCol(ColName: string; ColType: TAVType): TAVBaseColumn;
    begin
      Result := fColumns.Add(ColName, ColType);
    end;

    procedure TAVData.DeleteDisplayRows(indexList: TList<integer>);
    begin
      fRows.DeleteDisplayRows(indexList);
    end;

    procedure TAVData.DeleteDisplayRow(rowIndex: integer);
    begin
      fRows.DeleteDisplayRow(rowIndex);
    end;

    procedure TAVData.Sort(SortColumns: TArray<TAVBaseColumn>);
    begin
      fRows.Sort(SortColumns);
    end;

    procedure TAVData.Sort(SortColumns: TArray<string>);
    var i: integer;
        arr: TArray<TAVBaseColumn>;
        col: TAVBaseColumn;
    begin
      for i := Low(SortColumns) to High(SortColumns) do begin
        col := fColumns.ByName[SortColumns[i]];
        if col <> nil then begin
          SetLength(arr, Length(arr) + 1);
          arr[Length(arr) - 1] := col;
        end;
      end;
      if Length(arr) > 0 then Sort(arr);
    end;

    procedure TAVData.Sort(SortColumn: TAVBaseColumn);
    var arr: TArray<TAVBaseColumn>;
    begin
      SetLength(arr, 1);
      arr[0] := SortColumn;
      Sort(arr);
    end;

    procedure TAVData.Sort(SortColumn: string);
    var col: TAVBaseColumn;
    begin
      col := fColumns.ByName[SortColumn];
      if col <> nil then Sort(col);
    end;

    function TAVData.GetBookmark(row: integer): integer;
    begin
      Result := fRows.RowsIndex[row];
    end;

    function TAVData.FindRowByBookmark(bookmark: integer): integer;
    begin
      Result := fRows.RowsIndex.IndexOf(bookmark);
    end;

    procedure TAVData.SetFilter(Column: TAVBaseColumn; filter: string);
    begin
      if fColumns.IndexOf(Column) >= 0 then
      begin
        Column.FilterInfo.Filter := filter;
        fRows.Filter(Column);
      end;
    end;

    procedure TAVData.SetFilter(Column: string; filter: string);
    begin
      SetFilter(fColumns.ByName[Column], filter);
    end;

end.
